package org.snp

import org.slf4j.LoggerFactory
import java.awt.Point
import java.io.File

class Voters(file: File) {
  private val log = LoggerFactory.getLogger(this.getClass);
  //sort when we load, the once
  private val voters = sortByAddress(readCSV(file))


  //call some external class to read in CSV from hardcoded location
  private def readCSV(file: File): List[Voter] = {
    log.info("readCSV...")
    val voterReader = new VoterCSVReader(file)
    for (voter <- voterReader.voters) {
      log.debug("firstName:" + voter.firstName + " lastName: " + voter.lastName)
    }
    log.info("OK got " + voterReader.voters.size + " voters!")
    voterReader.voters
  }

  def generatePDF(points: List[Point]) {
    householdStats(voters)
    val generator: PDFMerger = new PDFMerger(points,
      interleave(points.size),
      new File("src/test/resources/Test_Survey.pdf"),
      new File("src/test/resources/Output.pdf"))
    generator.generate()
  }

  /**
   * For a given sorted list, create a report detailing number of voters & households per street run
   * Expects input to be sorted! Wiukd be nicer if this generated xls
   */
  private def householdStats(voters: List[Voter]) {
    //a street run is a given street, odds or evens
    var mvoters = voters
    var streetRuns: List[List[Voter]] = Nil
    while (mvoters != Nil) {
      val headEven = mvoters.head.streetNum % 2 == 0
      val headStreet = mvoters.head.streetName
      def streetEqual(v: Voter): Boolean = {
        val even = v.streetNum % 2 == 0
        val street = v.streetName
        even == headEven && street == headStreet
      }
      streetRuns ::= mvoters.takeWhile(v => streetEqual(v))
      mvoters = mvoters.dropWhile(v => streetEqual(v))
    }

    streetRuns.foreach {
      street =>
        val even = street.head.streetNum % 2 == 0
        val streetName = street.head.streetName
        val evenStr = if (even) {"evens"} else {"odds"}
        var households = 0
        var previous: Voter = new Voter()
        street.foreach {
          v =>
          //SOmetimes you get Flat 1-1 and Flat 1/1 in same household, so ignore that shit
            val aline1SansSep = if (v.aline1 != null) {v.aline1.replaceAll("-", "/")} else null
            val prevAline1SansSep = if (previous.aline1 != null) {previous.aline1.replaceAll("-", "/")} else null
            if (aline1SansSep != prevAline1SansSep || v.aline2.trim.toLowerCase != previous.aline2.trim.toLowerCase) {
              households = households + 1
            }
            previous = v
        }
        println(streetName + " " + evenStr + ": " + street.length + " voters and " + households + " households.")
    }
  }

  private def sortByAddress(voters: List[Voter]): List[Voter] = {
    var lst: List[Voter] = voters.sort((voter1, voter2) => {
      //log.debug("streetNum1:" + streetNum1 + " streetNum2:" + streetNum2 + " streetName1:" + streetName1 + " streetName2:" + streetName2)
      if (voter1.streetName == voter2.streetName) {
        if (voter1.streetNum == voter2.streetNum && voter1.streetNumChar.getOrElse("A") == voter2.streetNumChar.getOrElse("A")) {
          //log.debug("flatNum1:" + flatNum1.getOrElse(-1) + ":flatNum2:" + flatNum2.getOrElse(-1) + ":floorNum1:" + floorNum1.getOrElse(-1) + ":floorNum2:" + floorNum2.getOrElse(-1))
          //if we're on the same floor, compare by flat, otherwise by floor
          if (voter1.floorNum.getOrElse(-1) == voter2.floorNum.getOrElse(-1)) {
            voter1.flatNum.getOrElse(-1) < voter2.flatNum.getOrElse(-1)
          } else
            voter1.floorNum.getOrElse(-1) < voter2.floorNum.getOrElse(-1)
        }
        //odds before evens
        else if (voter1.streetNum % 2 == 0 && voter2.streetNum % 2 != 0)
          true
        else if (voter1.streetNum % 2 != 0 && voter2.streetNum % 2 == 0)
          false
        else if (voter1.streetNum == voter2.streetNum) { //order streets like 175B James Street correctly
          val ch1 = voter1.streetNumChar.getOrElse("A")
          val ch2 = voter2.streetNumChar.getOrElse("A")
          if (voter1.streetNum % 2 == 0)
            ch1 < ch2
          else
            ch1 > ch2
        } else if (voter1.streetNum % 2 == 0 && voter2.streetNum % 2 == 0)
          voter1.streetNum < voter2.streetNum
        else
          voter1.streetNum > voter2.streetNum
      } else {
        voter1.streetName < voter2.streetName
      }
    })

    lst.foreach(v => log.info(v.aline1 + ":" + v.aline2))

    //log.info("START:" + lst.head.aline1 + ":" + lst.head.aline2 )
    //log.info("END:" + lst.last.aline1 + ":" + lst.last.aline2 )
    lst
  }

  //interleave so that when printout is created user can just cut and stack
  def interleave(noBins: Int): List[Option[Voter]] = {
    var lst = voters
    var bins: Array[List[Voter]] = new Array(noBins)
    bins = bins.map(_ => Nil)
    var i = 0

    val length = if (voters.length % noBins > 0)
      voters.length / noBins + 1
    else voters.length / noBins

    for (i <- 0 until bins.length) {
      bins(i) = lst.take(length)
      lst = lst.drop(length)
    }

    //log.info("head of bin0:" + bins(0).head.aline1 + ":" + bins(0).head.aline2)

    //assemble new list from bins
    var joined: List[Option[Voter]] = Nil

    for (i <- 0 until voters.length) {
      if (bins(i % noBins) != Nil) {
        joined ::= Some(bins(i % noBins).head)
        bins(i % noBins) = bins(i % noBins).tail
      } else {
        //fill in with an empty
        joined ::= None
      }
    }
    //joined.foreach(v => log.info(v.aline1 + ":" + v.aline2))
    joined.reverse
  }
}
